{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 基于用户的协同过滤"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 导入工具包"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "# load数据（用户和物品索引，以及倒排表）\n",
    "import _pickle as cPickle\n",
    "\n",
    "# 稀疏矩阵，打分表\n",
    "import scipy.io as sio\n",
    "import os\n",
    "\n",
    "# 距离\n",
    "import scipy.spatial.distance as ssd\n",
    "import time\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 读入训练数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 用户和item的索引\n",
    "users_index = cPickle.load(open(\"./out/users_index.pkl\", 'rb'))\n",
    "items_index = cPickle.load(open(\"./out/items_index.pkl\", 'rb'))\n",
    "\n",
    "n_users = len(users_index)\n",
    "n_items = len(items_index)\n",
    "    \n",
    "# 倒排表\n",
    "# 每个用户打过分的电影\n",
    "user_items = cPickle.load(open(\"./out/user_items.pkl\", 'rb'))\n",
    "# 对每个电影打过分的用户\n",
    "item_users = cPickle.load(open(\"./out/item_users.pkl\", 'rb'))\n",
    "\n",
    "# 用户-物品关系矩阵R\n",
    "user_item_scores = sio.mmread(\"./out/user_item_scores\")\n",
    "user_item_scores = user_item_scores.tocsr()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 计算每个用户的平均打分"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "users_mu = np.zeros(n_users)\n",
    "for u in range(n_users):\n",
    "    n_user_items = 0\n",
    "    r_acc = 0.0\n",
    "    \n",
    "    for i in user_items[u]:  # 用户打过分的item\n",
    "        r_acc += user_item_scores[u, i]\n",
    "        n_user_items += 1\n",
    "    \n",
    "    users_mu[u] = r_acc / n_user_items\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 计算两个用户之间的相似度"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def user_similarity(uid1, uid2):\n",
    "    \"\"\"\n",
    "    计算两个User的相似性\n",
    "    :param uid1: 1号User的index\n",
    "    :param uid2: 2号User的index\n",
    "    :return: 两个User的相似度\n",
    "    \"\"\"\n",
    "    si = {}\n",
    "    for item in user_items[uid1]:\n",
    "        if item in user_items[uid2]:\n",
    "            si[item] = 1\n",
    "        \n",
    "    n = len(si)\n",
    "    if n == 0:\n",
    "        similarity = 0.0  \n",
    "        return similarity\n",
    "\n",
    "    # 用户uid1的有效打分(减去该用户的平均打分)\n",
    "    s1 = np.array([user_item_scores[uid1, item] - users_mu[uid1] for item in si])\n",
    "    \n",
    "    # 用户uid2的有效打分(减去该用户的平均打分)\n",
    "    s2 = np.array([user_item_scores[uid2,item]-users_mu[uid2] for item in si])\n",
    "    \n",
    "    similarity = 1 - ssd.cosine(s1, s2) \n",
    "    \n",
    "    if np.isnan(similarity): # s1或s2的l2模为0（全部等于该用户的平均打分）\n",
    "        similarity = 0.0\n",
    "\n",
    "    return similarity\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 预计算好所有用户之间的相似性\n",
    "对用户比较少、用户比较固定的的系统适用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "n_users=943\n\nui=0, cost_sec:0.002000093460083008, total_cost_sec:0.002000093460083008\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ui=100, cost_sec:65.77476191520691, total_cost_sec:65.77676200866699\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ui=200, cost_sec:49.607837438583374, total_cost_sec:115.38459944725037\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ui=300, cost_sec:58.164326906204224, total_cost_sec:173.5489263534546\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ui=400, cost_sec:63.93165683746338, total_cost_sec:237.48058319091797\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ui=500, cost_sec:73.70421552658081, total_cost_sec:311.1847987174988\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ui=600, cost_sec:51.23693060874939, total_cost_sec:362.42172932624817\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ui=700, cost_sec:41.100350856781006, total_cost_sec:403.5220801830292\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ui=800, cost_sec:24.21438503265381, total_cost_sec:427.736465215683\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ui=900, cost_sec:13.202754974365234, total_cost_sec:440.9392201900482\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "计算所有用户的相似性耗时:442.0673秒\n"
     ]
    }
   ],
   "source": [
    "users_similarity_matrix = np.matrix(np.zeros(shape=(n_users, n_users)), float)\n",
    "\n",
    "print(\"n_users={}\\n\".format(n_users))\n",
    "time_start = time.time()\n",
    "\n",
    "total_elapsed_time = 0\n",
    "each_time_start = time_start\n",
    "for ui in range(n_users):\n",
    "    users_similarity_matrix[ui, ui] = 1.0\n",
    "    \n",
    "    if ui % 100 == 0:\n",
    "        time_tick = time.time()\n",
    "        elapsed_time = time_tick - each_time_start\n",
    "        total_elapsed_time += elapsed_time\n",
    "        each_time_start = time_tick\n",
    "        print(\"ui={}, cost_sec:{}, total_cost_sec:{}\".format(\n",
    "            ui, elapsed_time, total_elapsed_time))\n",
    "\n",
    "    for uj in range(ui + 1, n_users):\n",
    "        similarity = user_similarity(ui, uj)\n",
    "        users_similarity_matrix[uj, ui] = similarity\n",
    "        users_similarity_matrix[ui, uj] = similarity\n",
    "\n",
    "time_end = time.time()\n",
    "elapsed_time = time_end - time_start\n",
    "print(\"计算所有用户的相似性耗时:{:.4f}秒\".format(elapsed_time))\n",
    "\n",
    "cPickle.dump(users_similarity_matrix, open(\"./out/users_similarity.pkl\", 'wb'))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 预测用户对item的打分"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 对给定用户，推荐物品/计算打分\n",
    "不同的推荐算法，只是预测打分函数不同，\n",
    "user_items_scores[i] = User_CF_pred(cur_user_id, i)  #预测打分\n",
    "\n",
    "如User_CF_pred, Item_CF_pred, svd_CF_pred,...\n",
    "甚至基于内容的推荐也是一样。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "def user_cf_pred(user_idx, item_idx):\n",
    "    \"\"\"\n",
    "    预测User对Item的打分\n",
    "    :param user_idx: User的index\n",
    "    :param item_idx: Item的index\n",
    "    :return: User对Item的预测打分\n",
    "    \"\"\"\n",
    "    total_weighted_score = 0.0\n",
    "    total_sim = 0.0\n",
    "    \n",
    "    user_idxs = item_users[item_idx]\n",
    "    for each_user_idx in user_idxs:\n",
    "        if user_idx == each_user_idx:\n",
    "            continue\n",
    "        \n",
    "        each_sim = users_similarity_matrix[user_idx, each_user_idx]\n",
    "        if np.isnan(each_sim) or each_sim == 0:\n",
    "            continue\n",
    "        \n",
    "        each_score = user_item_scores[each_user_idx, item_idx]\n",
    "        each_score = each_score - users_mu[each_user_idx]\n",
    "        total_weighted_score = total_weighted_score + each_sim * each_score\n",
    "        \n",
    "        total_sim = total_sim + abs(each_sim)\n",
    "        \n",
    "    if abs(total_sim) < 1e-6:\n",
    "        return users_mu[user_idx]\n",
    "    else:\n",
    "        return total_weighted_score * 1.0 / total_sim\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def recommend(user_id):\n",
    "    \"\"\"\n",
    "    返回推荐items及其打分（DataFrame）\n",
    "    :param user_id: User的userId\n",
    "    :return: 推荐结果列表\n",
    "    \"\"\"\n",
    "    cur_user_idx = users_index[user_id]\n",
    "    \n",
    "    # 训练集中该用户打过分的item\n",
    "    cur_user_item_idxs = user_items[cur_user_idx]\n",
    "\n",
    "    # 该用户对所有item的打分(注定是稀疏的)\n",
    "    cur_user_items_scores = np.zeros(n_items)\n",
    "    for each_item_idx in range(n_items):\n",
    "        if each_item_idx in cur_user_item_idxs:\n",
    "            continue\n",
    "        cur_user_items_scores[each_item_idx] = user_cf_pred(cur_user_idx,\n",
    "                                                            each_item_idx)\n",
    "    \n",
    "    # 推荐\n",
    "    item_score_list = [(e, i)\\\n",
    "                       for i, e in enumerate(list(cur_user_items_scores))]\n",
    "    sort_index = sorted(item_score_list, key=lambda x: x[0], reverse=True)\n",
    "    \n",
    "    columns = ['item_id', 'score']\n",
    "    df = pd.DataFrame(columns=columns)\n",
    "         \n",
    "    for each_score, each_item_idx in sort_index:\n",
    "        if np.isnan(each_score):\n",
    "            continue\n",
    "            \n",
    "        if each_item_idx in cur_user_item_idxs:\n",
    "            continue\n",
    "        \n",
    "        item_key_idx = list(items_index.values()).index(each_item_idx)\n",
    "        item_id = list(items_index.keys())[item_key_idx]\n",
    "        df.loc[len(df)] = [item_id, each_score]\n",
    "    \n",
    "    return df\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 读取测试数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 读取测试数据\n",
    "triplet_cols = ['user_id', 'item_id', 'rating', 'timestamp'] \n",
    "\n",
    "dpath = './data/u1.test'\n",
    "df_triplet_test = pd.read_csv(dpath, \n",
    "                              sep='\\t', \n",
    "                              names=triplet_cols, \n",
    "                              encoding='latin-1')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "def recommend_users(user_ids):\n",
    "    \"\"\"\n",
    "    为指定一批User推荐Item, 并返回推荐结果\n",
    "    :param user_ids: userId列表\n",
    "    :return: \n",
    "    \"\"\"\n",
    "    userid_rec_results = dict()\n",
    "    \n",
    "    time_start = time.time()\n",
    "    each_time_start = time_start\n",
    "    count = 0\n",
    "    \n",
    "    print(\"开始对{}个User计算推荐结果\".format(len(user_ids)))\n",
    "    for user_id in user_ids:\n",
    "        count = count + 1\n",
    "        if user_id not in users_index:\n",
    "            print(\"{} is a new user.\".format(user_id))\n",
    "            continue\n",
    "        \n",
    "        each_rec_items = recommend(user_id)\n",
    "        userid_rec_results[user_id] = each_rec_items\n",
    "        \n",
    "        if count % 100 == 0:\n",
    "            each_time_tick = time.time()\n",
    "            each_cost_time = each_time_tick - each_time_start\n",
    "            each_time_start = each_time_tick\n",
    "            print(\"user_index:{}, each_cost_time:{}\".format(\n",
    "                    count - 1, each_cost_time))\n",
    "            \n",
    "    time_end = time.time()\n",
    "    total_cost_time = time_end - time_start\n",
    "    print(\"完成{}个User的推荐, 共计耗时{}秒\".format(\n",
    "        count, total_cost_time\n",
    "    ))\n",
    "        \n",
    "    return userid_rec_results\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "开始对459个User计算推荐结果\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "user_index:99, each_cost_time:505.67792296409607\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "user_index:199, each_cost_time:510.02417182922363\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "user_index:299, each_cost_time:505.30590176582336\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "user_index:399, each_cost_time:494.7492980957031\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "完成459个User的推荐, 共计耗时2297.689420223236秒\n"
     ]
    }
   ],
   "source": [
    "# 测试集中的user_id集合\n",
    "unique_users_test = df_triplet_test['user_id'].unique()\n",
    "total_rec_results = recommend_users(unique_users_test)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {
    "scrolled": false,
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def evaluate(userid_rec_results, n_rec_items):\n",
    "    \"\"\"\n",
    "    为每个User截取若干条推荐结果，并衡量全体User的推荐效果\n",
    "    :param userid_rec_results:  推荐结果<userId -- 该user被推荐的全部Item>\n",
    "    :param n_rec_items:         每个所需截取的推荐结果的数量\n",
    "    :return: \n",
    "    \"\"\"\n",
    "    n_hits = 0              # TP\n",
    "    n_total_rec_items = 0   # TP+FP\n",
    "    n_total_test_items = 0  # TP+FN\n",
    "    all_rec_items = set()   # 所有User被推荐的Item的汇总\n",
    "    rss_test = 0.0          # 所有User的预测残差平方和\n",
    "\n",
    "    for user_id, rec_items in userid_rec_results.items():\n",
    "        user_records_test = df_triplet_test[df_triplet_test.user_id == user_id]\n",
    "        \n",
    "        # 累积TP和汇总所有User被推荐的指定数量的Item\n",
    "        for i in range(n_rec_items):\n",
    "            item_id = rec_items.iloc[i]['item_id']\n",
    "            if item_id in user_records_test['item_id'].values:\n",
    "                n_hits = n_hits + 1\n",
    "            all_rec_items.add(item_id)\n",
    "\n",
    "        # 累积预测评分残差平方和\n",
    "        for i in range(user_records_test.shape[0]):\n",
    "            item = user_records_test.iloc[i]['item_id']\n",
    "            score = user_records_test.iloc[i]['rating']\n",
    "            \n",
    "            df1 = rec_items[rec_items.item_id == item]\n",
    "            if df1.shape[0] == 0:\n",
    "                print(\"{} is a new item.\".format(item))\n",
    "                continue\n",
    "            \n",
    "            pred_score = df1['score'].values[0]\n",
    "            rss_test = rss_test + (pred_score - score)**2\n",
    "    \n",
    "        # 推荐的item总数(TP+FP)\n",
    "        n_total_rec_items = n_total_rec_items + n_rec_items\n",
    "    \n",
    "        # 真实item的总数(TP+FN)\n",
    "        n_total_test_items = n_total_test_items + user_records_test.shape[0]\n",
    "        \n",
    "    # Precision & Recall\n",
    "    precision = n_hits / (1.0 * n_total_rec_items)\n",
    "    recall = n_hits / (1.0 * n_total_test_items)\n",
    "    \n",
    "    # 覆盖度：推荐商品占总需要推荐商品的比例\n",
    "    coverage = len(all_rec_items) / (1.0 * n_items)\n",
    "    \n",
    "    # 打分的均方误差\n",
    "    rmse = np.sqrt(rss_test / df_triplet_test.shape[0])\n",
    "    \n",
    "    print(\"n_rec_items={}时:\\n \\tprecision={}\\n \"\n",
    "          \"\\trecall={}\\n \\tcoverage={}\\n \\trmse={}\".format(n_rec_items, \n",
    "                                                           precision, \n",
    "                                                           recall, \n",
    "                                                           coverage, \n",
    "                                                           rmse))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n推荐10个电影时的结果:\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "599 is a new item.\n711 is a new item."
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "814 is a new item.\n830 is a new item.\n852 is a new item.\n857 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1156 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1236 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1309 is a new item.\n1310 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1320 is a new item.\n1343 is a new item.\n1348 is a new item.\n1364 is a new item.\n1373 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1457 is a new item.\n1458 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1492 is a new item.\n1493 is a new item.\n1498 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1505 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1520 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1533 is a new item.\n1536 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1543 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1557 is a new item.\n1561 is a new item.\n1562 is a new item.\n1563 is a new item.\n1565 is a new item.\n1582 is a new item.\n1586 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "n_rec_items=10时:\n \tprecision=0.0008714596949891067\n \trecall=0.0002\n \tcoverage=0.14606060606060606\n \trmse=3.6861559305022356\n\n\n推荐20个电影时的结果\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "599 is a new item.\n711 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "814 is a new item.\n830 is a new item.\n852 is a new item.\n857 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1156 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1236 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1309 is a new item.\n1310 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1320 is a new item.\n1343 is a new item.\n1348 is a new item.\n1364 is a new item.\n1373 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1457 is a new item.\n1458 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1492 is a new item.\n1493 is a new item.\n1498 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1505 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1520 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1533 is a new item.\n1536 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1543 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1557 is a new item.\n1561 is a new item.\n1562 is a new item.\n1563 is a new item.\n1565 is a new item.\n1582 is a new item.\n1586 is a new item.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "n_rec_items=20时:\n \tprecision=0.0017429193899782135\n \trecall=0.0008\n \tcoverage=0.22727272727272727\n \trmse=3.6861559305022356\n"
     ]
    }
   ],
   "source": [
    "# 分别计算推荐10个电影和20个电影时的precision/recall/coverage/rmse\n",
    "print(\"\\n推荐10个电影时的结果:\")\n",
    "evaluate(total_rec_results, 10)\n",
    "\n",
    "print(\"\\n\\n推荐20个电影时的结果\")\n",
    "evaluate(total_rec_results, 20)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 测试，并计算评价指标\n",
    "PR、覆盖度、RMSE\n",
    "这部分代码所有的推荐算法相同\n",
    "\n",
    "令系统的用户集合为 U， R(u) 是根据用户在训练集上的行为给用户作出的推荐列表，而 T(u) 是用户在测试集上的行为列表。那么推荐结果的准确率定义为：\n",
    "$$\n",
    "Precision=\\frac{\\sum_{u\\in U}|R(u)\\cap T(u)|}{\\sum_{u\\in U}|R(u)|}\n",
    "$$\n",
    "推荐结果的召回率定义为：\n",
    "$$\n",
    "Recall=\\frac{\\sum_{u\\in U}|R(u)\\cap T(u)|}{\\sum_{u\\in U}|T(u)|}\n",
    "$$\n",
    "\n",
    "推荐系统的覆盖率为：\n",
    "$$\n",
    "Coverage=\\frac{\\sum_{u\\in U}|R(u)|}{|I|}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
